Binding an object sensitive property with a check button active property will look like this:
<object class="GtkButton" id="button">
<property name="sensitive" bind-source="checkbutton" bind-property="active"/>
</object>
This is based on the original work done by Denis Washington for his GSoC project
This closes Bug 654417 "[GSoC] Add <binding> element to GtkBuilder syntax"
* object has to be constructed before it can be used as the value of
* a construct-only property.
*
+ * It is also possible to bind a property value to another object's
+ * property value using the attributes
+ * "bind-source" to specify the source object of the binding,
+ * "bind-property" to specify the source property and optionally
+ * "bind-flags" to specify the binding flags
+ * Internally builder implement this using GBinding objects.
+ * For more information see g_object_bind_property()
+ *
* Signal handlers are set up with the <signal> element. The “name”
* attribute specifies the name of the signal, and the “handler” attribute
* specifies the function to connect to the signal. By default, GTK+ tries
GHashTable *callbacks;
GSList *delayed_properties;
GSList *signals;
+ GSList *bindings;
gchar *filename;
gchar *resource_prefix;
GType template_type;
continue;
}
}
+ else if (prop->bound && (!prop->data || *prop->data == '\0'))
+ {
+ /* Ignore properties with a binding and no value since they are
+ * only there for to express the binding.
+ */
+ continue;
+ }
else if (!gtk_builder_value_from_string (builder, pspec,
prop->data, ¶meter.value, &error))
{
g_object_set_data_full (object, "gtk-builder-name", g_strdup (name), g_free);
}
+static inline const gchar *
+object_get_name (GObject *object)
+{
+ if (GTK_IS_BUILDABLE (object))
+ return gtk_buildable_get_name (GTK_BUILDABLE (object));
+ else
+ return g_object_get_data (object, "gtk-builder-name");
+}
+
void
_gtk_builder_add_object (GtkBuilder *builder,
const gchar *id,
g_hash_table_insert (builder->priv->objects, g_strdup (id), g_object_ref (object));
}
+static inline void
+gtk_builder_take_bindings (GtkBuilder *builder,
+ GObject *target,
+ GSList *bindings)
+{
+ GSList *l;
+
+ for (l = bindings; l; l = g_slist_next (l))
+ {
+ BindingInfo *info = l->data;
+ info->target = target;
+ }
+
+ builder->priv->bindings = g_slist_concat (builder->priv->bindings, bindings);
+}
+
GObject *
_gtk_builder_construct (GtkBuilder *builder,
ObjectInfo *info,
}
g_array_free (parameters, TRUE);
+ if (info->bindings)
+ gtk_builder_take_bindings (builder, obj, info->bindings);
+
/* put it in the hash table. */
_gtk_builder_add_object (builder, info->id, obj);
g_slist_free (props);
}
+static inline void
+free_binding_info (gpointer data, gpointer user)
+{
+ BindingInfo *info = data;
+ g_free (info->target_property);
+ g_free (info->source);
+ g_free (info->source_property);
+ g_slice_free (BindingInfo, data);
+}
+
+static inline void
+gtk_builder_create_bindings (GtkBuilder *builder)
+{
+ GSList *l;
+
+ for (l = builder->priv->bindings; l; l = g_slist_next (l))
+ {
+ BindingInfo *info = l->data;
+ GObject *source;
+
+ if ((source = gtk_builder_get_object (builder, info->source)))
+ g_object_bind_property (source, info->source_property,
+ info->target, info->target_property,
+ info->flags);
+ else
+ g_warning ("Could not find source object '%s' to bind property '%s'",
+ info->source, info->source_property);
+
+ free_binding_info (info, NULL);
+ }
+
+ g_slist_free (builder->priv->bindings);
+ builder->priv->bindings = NULL;
+}
+
void
_gtk_builder_finish (GtkBuilder *builder)
{
gtk_builder_apply_delayed_properties (builder);
+ gtk_builder_create_bindings (builder);
}
/**
attribute translatable { "yes" | "no" } ?,
attribute comments { text } ?,
attribute context { text } ?,
+ (attribute bind-source { text },
+ attribute bind-property { text },
+ attribute bind-flags { text } ?) ?,
text ?
}
<text/>
</attribute>
</optional>
+ <optional>
+ <group>
+ <attribute name="bind-source">
+ <text/>
+ </attribute>
+ <attribute name="bind-property">
+ <text/>
+ </attribute>
+ <optional>
+ <attribute name="bind-flags">
+ <text/>
+ </attribute>
+ </optional>
+ </group>
+ </optional>
<optional>
<text/>
</optional>
GError **error)
{
PropertyInfo *info;
- gchar *name = NULL;
- gchar *context = NULL;
+ const gchar *name = NULL;
+ const gchar *context = NULL;
+ const gchar *bind_source = NULL;
+ const gchar *bind_property = NULL;
+ GBindingFlags bind_flags = G_BINDING_DEFAULT;
gboolean translatable = FALSE;
ObjectInfo *object_info;
int i;
for (i = 0; names[i] != NULL; i++)
{
if (strcmp (names[i], "name") == 0)
- name = g_strdelimit (g_strdup (values[i]), "_", '-');
+ name = values[i];
else if (strcmp (names[i], "translatable") == 0)
{
if (!_gtk_builder_boolean_from_string (values[i], &translatable,
}
else if (strcmp (names[i], "context") == 0)
{
- context = g_strdup (values[i]);
+ context = values[i];
+ }
+ else if (strcmp (names[i], "bind-source") == 0)
+ {
+ bind_source = values[i];
+ }
+ else if (strcmp (names[i], "bind-property") == 0)
+ {
+ bind_property = values[i];
+ }
+ else if (strcmp (names[i], "bind-flags") == 0)
+ {
+ if (!_gtk_builder_flags_from_string (G_TYPE_BINDING_FLAGS, values[i],
+ &bind_flags, error))
+ return;
}
else
{
return;
}
+ if (bind_source && bind_property)
+ {
+ BindingInfo *binfo = g_slice_new0 (BindingInfo);
+
+ binfo->target_property = g_strdup (name);
+ binfo->source = g_strdup (bind_source);
+ binfo->source_property = g_strdup (bind_property);
+ binfo->flags = bind_flags;
+
+ object_info->bindings = g_slist_prepend (object_info->bindings, binfo);
+ }
+ else if (bind_source || bind_property)
+ {
+ error_missing_attribute (data, element_name,
+ (bind_source) ? "bind-property" : "bind-source",
+ error);
+ return;
+ }
+
info = g_slice_new0 (PropertyInfo);
- info->name = name;
+ info->name = g_strdelimit (g_strdup (name), "_", '-');
info->translatable = translatable;
- info->context = context;
+ info->bound = (bind_source != NULL && bind_property != NULL);
+ info->context = g_strdup (context);
info->text = g_string_new ("");
state_push (data, info);
{
g_free (info->data);
g_free (info->name);
+ g_free (info->context);
+ /* info->text is already freed */
g_slice_free (PropertyInfo, info);
}
gchar *constructor;
GSList *properties;
GSList *signals;
+ GSList *bindings;
GObject *object;
CommonInfo *parent;
gboolean applied_properties;
gchar *name;
GString *text;
gchar *data;
- gboolean translatable;
+ gboolean translatable:1;
+ gboolean bound:1;
gchar *context;
} PropertyInfo;
gchar *connect_object_name;
} SignalInfo;
+typedef struct
+{
+ GObject *target;
+ gchar *target_property;
+ gchar *source;
+ gchar *source_property;
+ GBindingFlags flags;
+} BindingInfo;
+
typedef struct {
TagInfo tag;
gchar *library;
g_object_unref (builder);
}
+static void
+test_property_bindings (void)
+{
+ const gchar *buffer =
+ "<interface>"
+ " <object class=\"GtkWindow\" id=\"window\">"
+ " <child>"
+ " <object class=\"GtkVBox\" id=\"vbox\">"
+ " <property name=\"visible\">True</property>"
+ " <property name=\"orientation\">vertical</property>"
+ " <child>"
+ " <object class=\"GtkCheckButton\" id=\"checkbutton\">"
+ " <property name=\"active\">false</property>"
+ " </object>"
+ " </child>"
+ " <child>"
+ " <object class=\"GtkButton\" id=\"button\">"
+ " <property name=\"sensitive\" bind-source=\"checkbutton\" bind-property=\"active\" bind-flags=\"sync-create\">false</property>"
+ " </object>"
+ " </child>"
+ " <child>"
+ " <object class=\"GtkButton\" id=\"button2\">"
+ " <property name=\"sensitive\" bind-source=\"checkbutton\" bind-property=\"active\" />"
+ " </object>"
+ " </child>"
+ " </object>"
+ " </child>"
+ " </object>"
+ "</interface>";
+
+ GtkBuilder *builder;
+ GObject *checkbutton, *button, *button2, *window;
+
+ builder = builder_new_from_string (buffer, -1, NULL);
+
+ checkbutton = gtk_builder_get_object (builder, "checkbutton");
+ g_assert (GTK_IS_CHECK_BUTTON (checkbutton));
+ g_assert (!gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (checkbutton)));
+
+ button = gtk_builder_get_object (builder, "button");
+ g_assert (GTK_IS_BUTTON (button));
+ g_assert (!gtk_widget_get_sensitive (GTK_WIDGET (button)));
+
+ button2 = gtk_builder_get_object (builder, "button2");
+ g_assert (GTK_IS_BUTTON (button2));
+ g_assert (gtk_widget_get_sensitive (GTK_WIDGET (button2)));
+
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (checkbutton), TRUE);
+ g_assert (gtk_widget_get_sensitive (GTK_WIDGET (button)));
+ g_assert (gtk_widget_get_sensitive (GTK_WIDGET (button2)));
+
+ window = gtk_builder_get_object (builder, "window");
+ gtk_widget_destroy (GTK_WIDGET (window));
+ g_object_unref (builder);
+}
+
int
main (int argc, char **argv)
{
g_test_add_func ("/Builder/LevelBar", test_level_bar);
g_test_add_func ("/Builder/Expose Object", test_expose_object);
g_test_add_func ("/Builder/No IDs", test_no_ids);
+ g_test_add_func ("/Builder/Property Bindings", test_property_bindings);
return g_test_run();
}